package core;

import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.Label;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class FilterCoeffGenFIRWindowing implements FilterCoeffGen, ActionListener {

	public FilterType getFilterType() {
		return filterType;
	}

	public void setFilterType(FilterType filterType) {
		this.changed = true;
		this.filterType = filterType;
	}

	public WindowType getWindowType() {
		return windowType;
	}

	public void setWindowType(WindowType windowType) {
		this.changed = true;
		this.windowType = windowType;
	}

	public double getTransFreq1() {
		return transFreq1;
	}

	public void setTransFreq1(double transFreq1) {
		this.changed = true;
		this.transFreq1 = transFreq1;
	}

	public double getTransFreq2() {
		return transFreq2;
	}

	public void setTransFreq2(double transFreq2) {
		this.changed = true;
		this.transFreq2 = transFreq2;
	}

	public int getWindowLength() {
		return windowLength;
	}

	public void setWindowLength(int windowLength) {
		this.changed = true;
		this.windowLength = windowLength;
	}

	/**
	 * 
	 */
	private static final long serialVersionUID = -7872527656092411975L;

	public FilterCoeffGenFIRWindowing(FilterType filterType, WindowType windowType,
			double transFreq1, double transFreq2,
			int windowLength) {
		this.filterType = filterType;
		this.windowType = windowType;
		this.transFreq1 = transFreq1;
		this.transFreq2 = transFreq2;
		this.windowLength = windowLength;
	}
	
	public FilterCoeffGenFIRWindowing(){
		this.filterType = FilterType.LOW_PASS;
		this.windowType = WindowType.RECTANGULAR;
		this.transFreq1 = 100;
		this.transFreq2 = 200;
		this.windowLength = 101;
	}

	private FilterType filterType;
	private WindowType windowType;
	private boolean changed = true;
	private double transFreq1;
	private double transFreq2;
	private int windowLength;
	
	private transient JComboBox filterTypeField;
	private transient JComboBox windowTypeField;
	private transient JTextField transFreq1Field;
	private transient JTextField transFreq2Field;
	private transient JTextField windowLengthField;
	
	@Override
	public double[] getCoefficientsB(double sampleRate) {
		double[] coefficients;
		if (filterType == FilterType.LOW_PASS || filterType == FilterType.HIGH_PASS){
			coefficients = create1TransSinc(sampleRate);
		}
		else {
			coefficients = create2TransSinc(sampleRate);
		}
		
		double[] out = createWindow();
		for (int i=0 ; i<windowLength ; i++) {
			out[i] *= coefficients[i];
		}
		changed = false;
		return out;
	}
	
	@Override
	public double[] getCoefficientsA(double sampleRate) {
		return new double[]{1.0};
	}

	@Override
	public boolean isChanged() {
		return changed;
	}

	@Override
	public JPanel getPanel() {
		
		JPanel optionsPanel = new JPanel();
		
		FilterType[] filterTypes = new FilterType[] { 
				FilterType.BAND_PASS, FilterType.BAND_STOP, FilterType.HIGH_PASS, FilterType.LOW_PASS }; 
	
		WindowType[] windowTypes = new WindowType[] {
				WindowType.BARTLETT, WindowType.BLACKMAN, WindowType.HAMMING, WindowType.HANNING, WindowType.RECTANGULAR};
		
		
		filterTypeField = new JComboBox(filterTypes);
		filterTypeField.setSelectedItem(filterType);
		filterTypeField.addActionListener(this);
		
		windowTypeField = new JComboBox(windowTypes);
		windowTypeField.setSelectedItem(windowType);
		windowTypeField.addActionListener(this);
		
		transFreq1Field = new JTextField(""+getTransFreq1());
		transFreq1Field.addActionListener(this);
		transFreq2Field = new JTextField(""+getTransFreq2());
		transFreq2Field.addActionListener(this);
		windowLengthField = new JTextField(""+getWindowLength());
		windowLengthField.addActionListener(this);
		
		
		JButton applyButton = new JButton("Zastosuj");
		
		
		optionsPanel.setLayout(new BorderLayout());
		
		
		JPanel parametersPanel = new JPanel();
		parametersPanel.setLayout(new GridLayout(5,2));
		
		parametersPanel.add(new Label("Typ filtra"));
		parametersPanel.add(filterTypeField);
		
		parametersPanel.add(new Label("Typ okna"));
		parametersPanel.add(windowTypeField);
		
		parametersPanel.add(new Label("Czestotliwosc 1"));
		parametersPanel.add(transFreq1Field);
		
		parametersPanel.add(new Label("Czestotliwosc 2"));
		parametersPanel.add(transFreq2Field);
		
		parametersPanel.add(new Label("Rzad filtru"));
		parametersPanel.add(windowLengthField);

		
		optionsPanel.add(BorderLayout.CENTER, parametersPanel);
		
		applyButton.addActionListener(this);
		
		optionsPanel.add(BorderLayout.SOUTH, applyButton);
		return optionsPanel; //TODO
	}
	
	double[] create1TransSinc(double sampFreq)
	{
		int n;
		double[] window = new double[windowLength];
		// Calculate the normalised transistion frequency. As transFreq should be
		// less than or equal to sampFreq / 2, ft should be less than 0.5
		double ft = transFreq1 / sampFreq;

		double m_2 = 0.5 * (windowLength-1);
		int halfLength = windowLength / 2;

		// Set centre tap, if present
		// This avoids a divide by zero
		if (2*halfLength != windowLength) {
			double val = 2.0 * ft;

			// If we want a high pass filter, subtract sinc function from a dirac pulse
			if (filterType == FilterType.HIGH_PASS) val = 1.0 - val;

			window[halfLength] = val;
		}
		else if (filterType == FilterType.HIGH_PASS) {
			throw new RuntimeException("Filtr gornoprzepustowy musi byc parzystego stopnia");
		}

		// This has the effect of inverting all weight values
		if (filterType == FilterType.HIGH_PASS) ft = -ft;

		// Calculate taps
		// Due to symmetry, only need to calculate half the window
		for (n=0 ; n<halfLength ; n++) {
			double val = Math.sin(2.0 * Math.PI * ft * (n-m_2)) / (Math.PI * (n-m_2));

			window[n] = val;
			window[windowLength-n-1] = val;
		}

		return window;
	}

	// Create two sinc functions for filter with 2 transitions - Band pass and band stop filters
	double[] create2TransSinc(double sampFreq)
	{
		int n;
		double[] window = new double[windowLength];
		// Calculate the normalised transistion frequencies.
		double ft1 = transFreq1 / sampFreq;
		double ft2 = transFreq2 / sampFreq;

		double m_2 = 0.5 * (windowLength-1);
		int halfLength = windowLength / 2;

		// Set centre tap, if present
		// This avoids a divide by zero
		if (2*halfLength != windowLength) {
			double val = 2.0 * (ft2 - ft1);

			// If we want a band stop filter, subtract sinc functions from a dirac pulse
			if (filterType == FilterType.BAND_STOP) val = 1.0 - val;

			window[halfLength] = val;
		}
		else {
			throw new RuntimeException("Filtr pasmowy musi byc parzystego stopnia");
		}

		// Swap transition points if Band Stop
		if (filterType == FilterType.BAND_STOP) {
			double tmp = ft1;
			ft1 = ft2; ft2 = tmp;
		}

		// Calculate taps
		// Due to symmetry, only need to calculate half the window
		for (n=0 ; n<halfLength ; n++) {
			double val1 = Math.sin(2.0 * Math.PI * ft1 * (n-m_2)) / (Math.PI * (n-m_2));
			double val2 = Math.sin(2.0 * Math.PI * ft2 * (n-m_2)) / (Math.PI * (n-m_2));

			window[n] = val2 - val1;
			window[windowLength-n-1] = val2 - val1;
		}

		return window;
	}

	// Create a set of window weights
	// in - If not null, each value will be multiplied with the window weight
	// out - The output weight values, if NULL and new array will be allocated
	// windowLength - the number of weights
	// windowType - The window type
	double[] createWindow()
	{
		int n;
		int m = windowLength - 1;
		int halfLength = windowLength / 2;
		double[] out = new double[windowLength];
		

		// Calculate taps
		// Due to symmetry, only need to calculate half the window
		switch (windowType)
		{
			case RECTANGULAR:
				for (n=0 ; n<windowLength ; n++) {
					out[n] = 1.0;
				}
				break;

			case BARTLETT:
				for (n=0 ; n<=halfLength ; n++) {
					double tmp = (double) n - (double)m / 2;
					double val = 1.0 - (2.0 * Math.abs(tmp))/m;
					out[n] = val;
					out[windowLength-n-1] = val;
				}

				break;

			case HANNING:
				for (n=0 ; n<=halfLength ; n++) {
					double val = 0.5 - 0.5 * Math.cos(2.0 * Math.PI * n / m);
					out[n] = val;
					out[windowLength-n-1] = val;
				}

				break;

			case HAMMING:
				for (n=0 ; n<=halfLength ; n++) {
					double val = 0.54 - 0.46 * Math.cos(2.0 * Math.PI * n / m);
					out[n] = val;
					out[windowLength-n-1] = val;
				}
				break;

			case BLACKMAN:
				for (n=0 ; n<=halfLength ; n++) {
					double val = 0.42 - 0.5 * Math.cos(2.0 * Math.PI * n / m) + 0.08 * Math.cos(4.0 * Math.PI * n / m);
					out[n] = val;
					out[windowLength-n-1] = val;
				}
				break;
		}

		return out;
	}
	
	@Override
	public String toString() {
		return "FIR Windowing";
	}
	
	@Override
	public void actionPerformed(ActionEvent e) {
		setWindowLength((Integer.parseInt( windowLengthField.getText())));
		setTransFreq1((Double.parseDouble( transFreq1Field.getText())));
		setTransFreq2((Double.parseDouble( transFreq2Field.getText())));
		setFilterType((FilterType) filterTypeField.getSelectedItem());
		setWindowType((WindowType) windowTypeField.getSelectedItem());
	}

}
